/* eslint-disable no-use-before-define */
import { chain, many, optional, plus } from "../syntax-parser";
import { get } from "lodash";
import { createFourOperations } from "../base/four-operations";
import {
  comparisonOperator,
  dataType,
  udfLocationSym,
  logicalOperator,
  normalOperator,
  notOperator,
  numberSym,
  selectSpec,
  setValueList,
  stringOrWord,
  stringOrWordOrNumber,
  stringSym,
  wordSym,
  tableNameSym,
} from "../base/parser";
import { createTableName, flattenAll } from "../base/utils";
import { findNode } from "../syntax-parser/parser/utils";

export const root = () => {
  return chain(
    statements,
    optional(";")
  )((ast) => {
    return ast[0];
  });
};

const statements = () => {
  return chain(
    statement,
    many(
      chain(
        ";",
        statement
      )((ast) => {
        return ast[1];
      })
    )
  )(flattenAll);
};

const statement = () => {
  return chain([
    selectStatement,
    setStatement,
    insertStatement,
    deleteStatement,
    updateStatement,
    addUdfStatement,
    dropStatement,
    alterStatement,
    renameStatement,
    showStatement,
    describeStatement,
    explainStatement,
    useStatement,
    createTableStatement,
    createViewStatement,
    createIndexStatement,
    createFunctionStatement,
    createTemporaryFunctionStatement,
    createProcedureStatement,
    declareSetStatement,
    declareVarStatement,
    declareFromStatement,
    ifVarFunction,
    printStatement,
    whileStatement,
    beginStatement,
    returnStatement,
    hqlInsertStatement,
    hqlLoadDataStatement,
    hqlCreateTableStatement,
    envStatement,
    truncateStatement,
    commonTableExpression,
  ])((ast) => {
    return ast[0];
  });
};

// 设置字段值
const setFieldValue = () => {
  return chain(filedName, optional(chain("=", [stringSym, numberSym])))();
};

const setFieldValueList = () => {
  return chain(setFieldValue, many(",", setFieldValue))();
};

// --------------commen------------
// 字符集名称
const charset = () => {
  return chain(wordSym)((ast) => {
    return {
      type: "identifier",
      variant: "charset",
      name: ast[0],
    };
  });
};

// ----------------------------------- select statement -----------------------------------

const selectStatement = () => {
  return chain(
    "SELECT",
    selectList,
    optional(fromClause),
    optional(orderByClause),
    optional(limitClause),
    optional(union, selectStatement)
  )((ast) => {
    const result: any = {
      type: "statement",
      variant: "select",
      result: ast[1],
      from: ast[2],
    };
    if (ast[5]) {
      // eslint-disable-next-line prefer-destructuring
      result.union = ast[5];
    }

    return result;
  });
};

const union = () => {
  return chain("UNION", optional(["ALL", "DISTINCT"]))();
};

const fromClause = () => {
  return chain(
    "FROM",
    tableSources,
    optional(whereStatement),
    optional(groupByStatement),
    optional(havingStatement)
  )((ast) =>
    // TODO: IGNORE where group HAVING
    {
      return {
        sources: ast[1],
        where: ast[2],
        group: ast[3],
        HAVING: ast[4],
      };
    }
  );
};

const selectList = () => {
  return chain(selectField, many(selectListTail))(flattenAll);
};

const selectListTail = () => {
  return chain(
    ",",
    selectField
  )((ast) => {
    return ast[1];
  });
};

// selectField
//         ::= not? field alias?
//         ::= not? ( field ) alias?
//           | *
const selectField = () => {
  return chain([
    chain(
      many("NOT"),
      [
        chain(
          field,
          optional(overClause)
        )((ast) =>
          // TODO: IGNORE overClause
          {
            return ast[0];
          }
        ),
        chain("(", field, ")")(),
      ],
      optional(alias)
    )((ast) => {
      return {
        type: "identifier",
        variant: "column",
        name: ast[1],
        alias: ast[2],
      };
    }),
    "*",
  ])((ast) => {
    return ast[0];
  });
};

const whereStatement = () => {
  return chain(
    "WHERE",
    expression
  )((ast) => {
    return ast[1];
  });
};

// fieldList
//       ::= field (, fieldList)?
const fieldList = () => {
  return chain(columnField, many(",", columnField))();
};

const tableSources = () => {
  return chain(
    tableSource,
    many(
      chain(
        ",",
        tableSource
      )((ast) => {
        return ast[1];
      })
    )
  )(flattenAll);
};

const tableSource = () => {
  return chain(
    tableSourceItem,
    many(joinPart)
  )((ast) => {
    return {
      source: ast[0],
      joins: ast[1],
      type: "statement",
      variant: "tableSource",
    };
  });
};

const tableSourceItem = () => {
  return chain([
    chain(
      tableName,
      optional(alias)
    )((ast) => {
      return {
        type: "identifier",
        variant: "table",
        name: ast[0],
        alias: ast[1],
      };
    }),
    chain(
      [
        selectStatement,
        chain(
          "(",
          selectStatement,
          ")"
        )((ast) => {
          return ast[1];
        }),
      ],
      alias
    )((ast) => {
      return {
        ...ast[0],
        alias: ast[1],
      };
    }),
  ])((ast) => {
    return ast[0];
  });
};

const joinPart = () => {
  return chain(
    [
      "JOIN",
      "STRAINT_JOIN",
      chain(["INNER", "CROSS", "FULL"], "JOIN")(),
      chain(["LEFT", "RIGHT"], optional("OUTER"), "JOIN")(),
      chain(
        "NATURAL",
        optional(["LEFT", "RIGHT"], optional("OUTER")),
        "JOIN"
      )(),
    ],
    tableSourceItem,
    optional("ON", expression)
  )((ast) => {
    return {
      type: "statement",
      variant: "join",
      join: ast[1],
      conditions: ast[2],
    };
  });
};

// Alias ::= AS WordOrString
//         | WordOrString
const alias = () => {
  return chain([
    chain(
      "AS",
      stringOrWord
    )((ast) => {
      return ast[1];
    }),
    stringOrWord,
  ])((ast) => {
    return ast[0];
  });
};

// ----------------------------------- CREATE TABLE statement -----------------------------------
const partitionByField = () => {
  return chain(
    "PARTITIONED",
    "BY",
    "(",
    chain(wordSym, dataType),
    many(",", chain(wordSym, dataType)),
    ")"
  )();
};
const stringKeyValue = () => {
  return chain(stringSym, "=", stringSym)();
};
const createTableWidth = () => {
  return chain(
    "WITH",
    ["SERDEPROPERTIES", "TBLPROPERTIES"],
    "(",
    stringKeyValue,
    many(",", stringKeyValue),
    ")"
  )();
};

const hqlCreateTableStatement = () => {
  return chain(
    "CREATE",
    optional("EXTERNAL"),
    "TABLE",
    optional("IF", "NOT", "EXISTS"),
    [
      chain(
        tableName,
        optional("(", tableOptions, ")", optional(partitionByField)),
        optional("COMMENT", stringSym),
        optional(
          "ROW",
          "FORMAT",
          "DELIMITED",
          optional("FIELDS", "TERMINATED", "BY", [
            "','",
            "'\t'",
            "':'",
            "';'",
            "'!'",
            "'|'",
            "' '",
            stringSym,
          ]),
          optional("LINES", "TERMINATED", "BY", ["\n", stringSym])
        ),
        optional("STORED", [
          chain("BY", stringSym),
          chain("AS", [
            "TEXTFILE",
            chain(
              "INPUTFORMAT",
              stringSym,
              optional("OUTPUTFORMAT", stringSym)
            ),
            chain(
              "OUTPUTFORMAT",
              stringSym,
              optional("INPUTFORMAT", stringSym)
            ),
            "SEQUENCEFILE",
            "RCFILE",
            "AVRO",
            "PARQUET",
            "ORC",
          ]),
        ]),
        optional("AS", selectStatement),
        optional(createTableWidth, optional(createTableWidth))
      ),
      chain(tableName, optional("LIKE", tableName)),
    ]
  )();
};

const createTableStatement = () => {
  return chain(
    "CREATE",
    optional("EXTERNAL"),
    "TABLE",
    optional("IF", "NOT", "EXISTS"),
    [
      chain(
        tableName,
        optional("(", tableOptions, ")", optional(withStatement))
      ),
      chain(tableName, optional("LIKE", tableName)),
    ]
  )();
};

const withStatement = () => {
  return chain("WITH", "(", withStatements, ")")();
};

const withStatements = () => {
  return chain(withStatementsTail, many(",", withStatementsTail))();
};

const withStatementsTail = () => {
  return chain(wordSym, "=", stringSym)();
};

const tableOptions = () => {
  return chain(tableOption, many(",", tableOption))();
};
// 不支持check写法
const tableOption = () => {
  return chain([
    chain(
      stringOrWord,
      dataType,
      many([
        "UNSIGNED",
        [chain("UNIQUE", optional("INDEX")), chain("PRIMARY", "KEY")],
        "AUTO_INCREMENT",
        chain("DEFAULT", [
          chain(
            "CURRENT_TIMESTAMP",
            optional("ON", "UPDATE", "CURRENT_TIMESTAMP")
          ),
          stringSym,
          numberSym,
          "NULL",
        ]),
        chain("NOT", "NULL"),
        chain("COMMENT", stringSym),
      ])
    ),
    chain(
      "FOREIGN",
      "KEY",
      "(",
      wordSym,
      ")",
      "REFERENCES",
      tableName,
      "(",
      wordSym,
      ")"
    ),
  ])();
};

const primaryKeyList = () => {
  return chain(wordSym, optional(",", primaryKeyList))();
};

const tableName = () => {
  return chain([
    chain(tableNameSym)(),
    chain(
      tableNameSym,
      ".",
      tableNameSym
    )((ast) => {
      return [ast[0], ast[2]];
    }),
    chain(
      tableNameSym,
      ".",
      tableNameSym,
      ".",
      tableNameSym
    )((ast) => {
      return [ast[0], ast[2], ast[4]];
    }),
  ])((ast) => {
    if (ast[0].length === 1) {
      return createTableName({
        cluster: ast[0][0],
        database: null,
        tableName: null,
      });
    }
    if (ast[0].length === 2) {
      return createTableName({
        cluster: ast[0][0],
        database: ast[0][1],
        tableName: null,
      });
    }
    if (ast[0].length === 3) {
      return createTableName({
        cluster: ast[0][0],
        database: ast[0][1],
        tableName: ast[0][2],
      });
    }
  });
};

// 字段名称
const filedName = () => {
  return chain(stringOrWord)((ast) => {
    return {
      type: "identifier",
      variant: "column",
      name: ast[0],
    };
  });
};

// 分区字段
const partitionField = () => {
  return chain(stringOrWord)((ast) => {
    return {
      type: "identifier",
      variant: "partition",
      name: ast[0],
    };
  });
};
// --------------------- ENV cluster ----------------------------
const envStatement = () => {
  return chain(
    "ENV",
    tableNameSym
  )((ast) => {
    return {
      type: "identifier",
      variant: "env",
      name: ast[1],
    };
  });
};
// ----------------------------------- DECLARE ------------------
const declareVarTail = () => {
  return chain(wordSym, dataType, optional("=", stringOrWordOrNumber))();
};
const declareVarStatement = () => {
  return chain("DECLARE", declareVarTail, many(",", declareVarTail))();
};
const declareFromStatement = () => {
  return chain("DECLARE", wordSym, "FOR", selectStatement)();
};
const declareSetStatement = () => {
  return chain(
    "DECLARE",
    "CONTINUE",
    "HANDLER",
    "FOR",
    [
      chain("NOT", "FOUND"),
      "SQLEXCEPTION",
      "SQLWARNING",
      chain("SQLSTATE", [
        "'00000'",
        "'22001'",
        "'22003'",
        "'22007'",
        "'22012'",
        "'23000'",
        "'42000'",
        "'42S02'",
        stringSym,
      ]),
    ],
    "SET",
    wordSym,
    "=",
    stringOrWordOrNumber
  )();
};

// ----------------------------------- HAVING --------------------------------------------------
const havingStatement = () => {
  return chain("HAVING", expression)();
};

// ----------------------------------- CREATE VIEW statement -----------------------------------
const createViewStatement = () => {
  return chain("CREATE", "VIEW", wordSym, "AS", selectStatement)();
};

// ----------------------------------- CREATE PROCEDURE ------------------------------
const procedureParam = () => {
  return chain(procedureParamTail, many(",", procedureParamTail))();
};
const procedureParamTail = () => {
  return chain(["IN", "INOUT"], wordSym, dataType)();
};
const createProcedureStatement = () => {
  return chain(
    "CREATE",
    "PROCEDURE",
    wordSym,
    "(",
    optional(procedureParam),
    ")"
  )();
};

// ----------------------------------- INSERT statement -----------------------------------
// 动态分区
const dynamicPartition = () => {
  return chain(wordSym, optional(chain("=", stringOrWordOrNumber)))();
};
const hqlInsertStatement = () => {
  return chain(
    "INSERT",
    ["INTO", "OVERWRITE"],
    "TABLE",
    tableName,
    optional(
      optional(
        "PARTITION",
        "(",
        dynamicPartition,
        many(",", dynamicPartition),
        ")"
      ),
      selectStatement
    )
  )();
};

const hqlLoadDataStatement = () => {
  return chain(
    "LOAD",
    "DATA",
    optional("LOCAL"),
    "INPATH",
    stringSym,
    ["OVERWRITE"],
    "INTO",
    "TABLE",
    tableName,
    optional(
      "PARTITION",
      "(",
      partitionKeyValue,
      many(",", partitionKeyValue),
      ")"
    )
  )();
};
const insertStatement = () => {
  return chain(
    "INSERT",
    optional("IGNORE"),
    "INTO",
    tableName,
    optional(selectFieldsInfo),
    optional(
      "VALUES",
      "(",
      stringOrWordOrNumber,
      many(",", stringOrWordOrNumber),
      ")"
    ),
    optional(selectStatement)
  )((ast) => {
    return {
      type: "statement",
      variant: "insert",
      into: {
        type: "indentifier",
        variant: "table",
        name: ast[3],
      },
      result: ast[5],
    };
  });
};

const selectFieldsInfo = () => {
  return chain("(", selectFields, ")")();
};

const selectFields = () => {
  return chain(wordSym, many(",", wordSym))();
};

// ----------------------------------- groupBy -----------------------------------
const groupByStatement = () => {
  return chain("GROUP", "BY", fieldList)();
};

// ----------------------------------- orderBy -----------------------------------
const orderByClause = () => {
  return chain(["ORDER", "SORT"], "BY", orderByExpressionList)();
};

const orderByExpressionList = () => {
  return chain(orderByExpression, many(",", orderByExpression))();
};

const orderByExpression = () => {
  return chain(expression, optional(["ASC", "DESC"]))();
};

/*
<PARTITION BY clause> ::=  
PARTITION BY value_expression , ... [ n ] 
*/

// over 子句中的 partition distribute
const overPartitionByClause = () => {
  return chain([
    wordSym,
    chain(["PARTITION", "DISTRIBUTE"], "BY", optional(expression))(),
  ])((ast) => {
    return ast;
  });
};
const partitionKeyValue = () => {
  return chain(wordSym, "=", stringOrWordOrNumber)();
};
const partitionBySet = () => {
  return chain(
    "PARTITION",
    "(",
    partitionKeyValue,
    many(",", partitionKeyValue),
    ")"
  )();
};

/*
OVER (   
       [ <PARTITION BY clause> ]  
       [ <ORDER BY clause> ]   
       [ <ROW or RANGE clause> ]  
      )  
*/
const overClause = () => {
  return chain("OVER", "(", overTailExpression, ")")();
};

const overTailExpression = () => {
  return chain(
    [
      chain(overPartitionByClause, chain(field, orderByClause)),
      chain(field, orderByClause),
    ],
    many(",", overTailExpression)
  )();
};

// ----------------------------------- limit -----------------------------------
const limitClause = () => {
  return chain("LIMIT", [
    numberSym,
    chain(numberSym, ",", numberSym)(),
    chain(numberSym, "OFFSET", numberSym)(),
  ])();
};

// ----------------------------------- Function -----------------------------------
const functionChain = () => {
  return chain([castFunction, normalFunction, ifFunction])((ast) => {
    return ast[0];
  });
};

const ifFunction = () => {
  return chain(
    "IF",
    "(",
    predicate,
    ",",
    field,
    ",",
    field,
    ")"
  )((ast) => {
    return {
      type: "function",
      name: "if",
      args: [ast[2], ast[4], ast[6]],
    };
  });
};

const ifVarFunction = () => {
  return chain(
    "IF",
    wordSym,
    "=",
    stringOrWordOrNumber,
    "THEN",
    statements,
    optional(";", elseStatement),
    optional(";", endIfStatement)
  )();
};

const printStatement = () => {
  return chain(
    "PRINT",
    [stringOrWordOrNumber, castFunction],
    many("+", [stringOrWordOrNumber, castFunction])
  )();
};

const elseStatement = () => {
  return chain("ELSE", printStatement)();
};

const endIfStatement = () => {
  return chain("END", "IF")();
};

const returnStatement = () => {
  return chain("RETURN", stringOrWordOrNumber)();
};

const beginStatement = () => {
  return chain("BEGIN", statements, chain(";", "END"))();
};

const castFunction = () => {
  return chain(
    "CAST",
    "(",
    field,
    "AS",
    dataType,
    ")"
  )((ast) => {
    return {
      type: "function",
      name: "cast",
      args: [ast[2], ast[4]],
    };
  });
};

const normalFunction = () => {
  return chain(
    wordSym,
    "(",
    optional(functionFields),
    ")",
    optional("FILTER", "(", whereStatement, ")")
  )((ast) => {
    return {
      type: "function",
      name: ast[0],
      args: ast[2],
    };
  });
};

const functionFields = () => {
  return chain(functionFieldItem, many(",", functionFieldItem))();
};

const functionFieldItem = () => {
  return chain(many(selectSpec), [columnField, caseStatement])((ast) => {
    return ast;
  });
};

// ----------------------------------- Case -----------------------------------
const caseStatement = () => {
  return chain(
    "CASE",
    plus(caseAlternative),
    optional("ELSE", [columnField, "NULL"]),
    ["END", chain("END", "AS", wordSym)()]
  )();
};

const caseAlternative = () => {
  return chain("WHEN", expression, "THEN", fieldItem)();
};

// ----------------------------------- set statement -----------------------------------

const setStatement = () => {
  return chain("SET", variableAssignments)();
};

const variableAssignments = () => {
  return chain(variableAssignment, many(",", variableAssignment))();
};

const variableAssignment = () => {
  return chain(variableLeftValue, "=", [
    "TRUE",
    "FALSE",
    stringSym,
    numberSym,
    wordSym,
    variableLeftValue,
  ])();
};

const variableLeftValue = () => {
  return chain(wordSym, many(".", wordSym))();
};

// ----------------------------------- Expression -----------------------------------

/*
 * expr:
 *   expr OR expr
 * | expr || expr
 * | expr XOR expr
 * | expr AND expr
 * | expr && expr
 * | NOT expr
 * | ! expr
 * | boolean_primary IS [NOT] {TRUE | FALSE | UNKNOWN}
 * | boolean_primary
 * */

const expression = () => {
  return chain(expressionHead, many(logicalOperator, expression))(flattenAll);
};

const expressionHead = () => {
  return chain(
    [
      chain("(", expression, ")")(),
      chain(notOperator, expression)(),
      chain(booleanPrimary),
    ],
    optional(chain("IS", optional("NOT"), ["TRUE", "FALSE", "UNKNOWN"])())
  )((ast) => {
    return ast[0];
  });
};

// /*
//  *boolean_primary:
//  *   boolean_primary IS [NOT] NULL
//  * | boolean_primary <=> predicate
//  * | boolean_primary comparison_operator predicate
//  * | boolean_primary comparison_operator {ALL | ANY} (subquery)
//  * | predicate
// **/
const booleanPrimary = () => {
  return chain(
    predicate,
    many([
      "ISNULL",
      chain([chain("IS", "NOT")(), "IS", "NOT"], ["NULL", columnField])(),
    ])
  )(flattenAll);
};

/*
 * predicate:
 *    field SOUNDS LIKE field
 *  | field [NOT] IN (subquery)
 *  | field [NOT] IN (expr [, expr] ...)
 *  | field [NOT] BETWEEN field AND predicate
 *  | field [NOT] LIKE simple_expr [ESCAPE simple_expr]
 *  | field [NOT] REGEXP field
 *  | field
 * */
const predicate = () => {
  return chain([
    chain(columnField, predicateAddonComparison)(),
    chain("(", predicate, ")", predicateAddonComparison)(),
  ])();
};

const predicateAddonComparison = () => {
  return chain(
    optional([
      chain(comparisonOperator, columnField)(),
      chain("SOUNDS", "LIKE", columnField)(),
      isOrNotExpression,
    ]),
    optional(["OR", predicate])
  )();
};

const columnField = () => {
  return chain(field)((ast) => {
    return {
      type: "identifier",
      variant: "column",
      name: ast[0],
    };
  });
};

const isOrNotExpression = () => {
  return chain(optional("IS"), optional("NOT"), [
    chain("IN", "(", [selectStatement, fieldList], ")")(),
    chain("BETWEEN", field, "AND", predicate)(),
    chain("LIKE", field, optional("ESCAPE", field))(),
    chain("REGEXP", field)(),
    "NULL",
  ])();
};

const fieldItem = () => {
  return chain(
    fieldItemDetail,
    many(normalOperator, fieldItemDetail)
  )((ast) => {
    if (!ast[1]) {
      return ast[0];
    }
    return [ast[0], ast[1]];
  });
};

const fieldItemDetail = () => {
  return chain([
    functionChain,
    caseStatement,
    chain(
      stringOrWordOrNumber,
      optional([
        chain(
          ".",
          "*"
        )(() => {
          return {
            type: "identifier",
            variant: "groupAll",
          };
        }),
        chain(":", normalFunction)(),
        dotStringOrWordOrNumber,
      ])
    )((ast) => {
      if (!ast[1]) {
        return ast[0];
      }

      return {
        ...ast[1],
        groupName: ast[0],
      };
    }),
    "*",
  ])((ast) => {
    return ast[0];
  });
};

const dotStringOrWordOrNumber = () => {
  return chain(".", [
    stringSym,
    numberSym,
    chain(wordSym)((ast) => {
      return {
        type: "identifier",
        variant: "columnAfterGroup",
        name: ast[0],
      };
    }),
  ])((ast) => {
    return ast[1];
  });
};

const field = () => {
  return createFourOperations(fieldItem)();
};

// ----------------------------------- CREATE index expression -----------------------------------
const createIndexStatement = () => {
  return chain("CREATE", "INDEX", indexItem, onStatement, whereStatement)();
};

const indexItem = () => {
  return chain(stringSym, many(".", stringSym))();
};

const onStatement = () => {
  return chain("ON", stringSym, "(", fieldForIndexList, ")")();
};

const fieldForIndex = () => {
  return chain(stringSym, optional(["ASC", "DESC"]))();
};

const fieldForIndexList = () => {
  return chain(fieldForIndex, many(",", fieldForIndex))();
};

// ----------------------------------- CREATE function expression -----------------------------------
const createFunctionStatement = () => {
  return chain("CREATE", "FUNCTION", wordSym, [
    chain("AS", stringSym),
    chain(
      [
        // MySQL
        chain(
          "(",
          optional(
            chain(wordSym, dataType),
            many(",", chain(wordSym, dataType))
          ),
          ")"
        ),
        // SQL Server
        optional(chain(wordSym, dataType), many(",", chain(wordSym, dataType))),
      ],
      chain("RETURNS", dataType),
      optional(["DETERMINISTIC", "AS", "IS"]),
      beginStatement
    ),
  ])();
};

// ----------------------------------- update statement -----------------------------------
const updateStatement = () => {
  return chain(
    "UPDATE",
    tableName,
    optional(chain("SET", setFieldValueList, optional(whereStatement)))
  )((ast) => {
    return {
      type: "statement",
      variant: "update",
      tableInfo: ast[1],
      set: get(ast, [2, 1]),
      where: get(ast, [2, 2]),
    };
  });
};
// ------------------------------- delete satement --------------------------
// DELETE FROM `${1:TABLE}`${2: WHERE `$3` = '$4'};
const deleteStatement = () => {
  return chain(
    "DELETE",
    "FROM",
    tableName,
    optional(whereStatement)
  )((ast) => {
    return {
      type: "statement",
      variant: "delete",
      tableInfo: ast[2],
      where: ast[3],
    };
  });
};

// ------------------- add UDF satement-----------------
/**
add jar hdfs://test-cluster//tmp/udf/01386141/43948/1000/eos-naga-pcm-calc-1.29-sit.jar;
CREATE temporary function ceshi_a AS 'ceshi2';
 */
const udfNameSource = () => {
  return chain([
    chain(udfLocationSym)((ast) => {
      return {
        type: "identifier",
        variant: "udfName",
        name: ast[0],
        alias: ast[1],
      };
    }),
  ])((ast) => {
    return ast[0];
  });
};

const addUdfStatement = () => {
  return chain(
    "ADD",
    udfNameSource
  )((ast) => {
    const udf = get(ast, [1]);
    const result = {
      type: "statement",
      variant: "addUdf",
      udf: udf?.variant === "udfName" ? udf : undefined,
    };
    return result;
  });
};

const createTemporaryFunctionStatement = () => {
  return chain("CREATE", "TEMPORARY", "FUNCTION", wordSym, "AS", stringSym)();
};
// -------- DROP statement ------------
const dropStatement = () => {
  return chain("DROP", [
    chain("DATABASE", tableName),
    chain("TABLE", optional(chain("IF", "EXISTS")), tableName),
    chain("INDEX", optional(chain("IF", "EXISTS")), wordSym, "ON", tableName),
    chain("VIEW", optional(chain("IF", "EXISTS")), wordSym),
    chain("PROCEDURE", optional(chain("IF", "EXISTS")), wordSym),
    chain("FUNCTION", optional(chain("IF", "EXISTS")), wordSym),
    chain("TRIGGER", optional(chain("IF", "EXISTS")), wordSym),
    chain("USER", optional(chain("IF", "EXISTS")), wordSym),
  ])((ast) => {
    const tableInfo = findNode(ast, "tableName");
    const result = {
      type: "statement",
      variant: "drop",
      tableInfo: tableInfo || undefined,
    };
    return result;
  });
};

// -------------- LOOP ---------------
const whileStatement = () => {
  return chain("WHILE", expression, statement)();
};

// ------------- ALTER statement ------------
// const selectList = () => {
//   return chain(selectField, many(selectListTail))(flattenAll);
// };

// const selectListTail = () => {
//   return chain(
//     ",",
//     selectField
//   )((ast) => {
//     return ast[1];
//   });
// };

const filedNameList = () => {
  return chain(filedNameItem, many(filedNameItemTail))(flattenAll);
};
const filedNameItemTail = () => {
  return chain(
    ",",
    filedNameItem
  )((ast) => {
    return ast[1];
  });
};
const filedNameItem = () => {
  return chain(filedName)((ast) => {
    return ast[0];
  });
};

const filedNameWidthAscList = () => {
  return chain(
    filedNameWidthAscItem,
    many(filedNameWidthAscListTail)
  )(flattenAll);
};

const filedNameWidthAscListTail = () => {
  return chain(
    ",",
    filedNameWidthAscItem
  )((ast) => {
    return ast[1];
  });
};
const filedNameWidthAscItem = () => {
  return chain(
    filedName,
    optional(["ASC", "DESC"])
  )((ast) => {
    return ast[0];
  });
};
const characterStatement = () => {
  return chain(
    "CHARACTER",
    "SET",
    ["utf8", "utf8mb4", "latin1", "gbk", "big5", wordSym],
    optional("COLLATE", [
      "utf8_general_ci",
      "utf8_unicode_ci",
      "utf8mb4_general_ci",
      "utf8mb4_unicode_ci",
      wordSym,
    ])
  )();
};
const alertTableItem = () => {
  return chain([
    chain("ADD", [
      // add column
      chain(optional("COLUMN"), optional("IF", "NOT", "EXISTS"), [
        chain(
          stringOrWord,
          dataType,
          optional([
            chain(
              "NOT",
              "NULL",
              optional(chain("DEFAULT", stringOrWordOrNumber))
            ),
            chain("DEFAULT", stringOrWordOrNumber, optional("NOT", "NULL")),
          ]),
          optional([chain("AFTER", filedName), "FIRST"])
        ),
        chain([
          many(partitionBySet),
          chain(partitionBySet, "LOCATION", stringSym),
        ]),
      ]),
      // add index
      chain("INDEX", wordSym, "(", filedNameWidthAscList, optional(")")),
      // 其他
      chain(
        ["FULLTEXT", "UNIQUE", "SPATIAL"],
        optional(wordSym),
        "(",
        filedNameWidthAscList,
        optional(")")
      ),
      //add primary key
      chain(
        optional("CONSTRAINT"),
        "PRIMARY",
        "KEY",
        "(",
        filedNameList,
        optional(")")
      ),
      // add foreing key
      chain(
        optional("CONSTRAINT", wordSym),
        "FOREIGN",
        "KEY",
        "(",
        filedName,
        optional(
          ")",
          "REFERENCES",
          tableName,
          optional(
            "(",
            filedName,
            ")",
            many([
              chain("ON", "DELETE", "NO", "ACTION"),
              chain("ON", "UPDATE", "CASCADE"),
              chain("ON", "DELETE", "SET", "NULL"),
              chain("ON", "DELETE", "CASCADE"),
              chain("ON", "UPDATE ", "SET", "NULL"),
              chain("ON", "UPDATE", "SET", "DEFAULT"),
            ])
          )
        )
      ),
      // add partition
    ]),
    // auto_increment
    chain("AUTO_INCREMENT", "=", numberSym),
    // modify
    chain(
      "MODIFY",
      filedName,
      optional(
        dataType,
        optional([
          chain("NOT", "NULL", optional("DEFAULT", stringOrWordOrNumber)),
          chain("DEFAULT", stringOrWordOrNumber, optional("NOT", "NULL")),
        ]),
        optional(["FIRST", chain("AFTER", filedName)])
      )
    ),
    // change
    chain(
      "CHANGE",
      filedName,
      optional(
        wordSym,
        dataType,
        optional([
          chain("NOT", "NULL", optional("DEFAULT", stringOrWordOrNumber)),
          chain("DEFAULT", stringOrWordOrNumber, optional("NOT", "NULL")),
        ]),
        optional(["FIRST", chain("AFTER", filedName)])
      )
    ),
    // rename
    chain("RENAME", "TO", wordSym),
    // drop
    chain("DROP", [
      // drop column
      chain("COLUMN", filedName),
      // drop index
      chain("INDEX", wordSym),
      // drop FOREIGN KEY fk_id;
      chain("FOREIGN", "KEY", wordSym),
      // drop partition
      chain(optional("IF", "EXISTS"), partitionBySet),
    ]),
    // character
    characterStatement,
  ])((ast) => {
    return ast;
  });
};

const alertTableList = () => {
  return chain(alertTableItem, many(alertTableItemTail))(flattenAll);
};
const alertTableItemTail = () => {
  return chain(
    ",",
    alertTableItem
  )((ast) => {
    return ast[1];
  });
};

const alterStatement = () => {
  return chain("ALTER", [
    chain("TABLE", tableName, optional(alertTableList)),
    chain(
      "DATABASE",
      tableName,
      optional([
        chain("DEFAULT", chain("CHARACTER", "SET", charset)),
        characterStatement,
      ])
    ),
    chain("INDEX", wordSym, "ON", tableName, optional("DROP")),
  ])((ast) => {
    const tableInfo = findNode(ast, "tableName");
    const filedNames = findNode(ast, "column");
    if (filedNames) {
      filedNames.forEach((item, index) => {
        item.name.index = index;
        item.name.statement = "alertStatement";
      });
    }
    const charsets = findNode(ast, "charset");
    const result = {
      type: "statement",
      variant: "alert",
      tableInfo: tableInfo || undefined,
      filedName: filedNames || undefined,
      charset: charsets || undefined,
    };
    return result;
  });
};

const renameStatement = () => {
  return chain(
    "RENAME",
    tableName,
    optional("TO", wordSym)
  )((ast) => {
    const tableInfo = get(ast, [1]);
    const result = {
      type: "statement",
      variant: "rename",
      tableInfo: tableInfo?.variant === "tableName" ? tableInfo : undefined,
    };
    return result;
  });
};

// ---------- showStatement -------

const showStatement = () => {
  return chain("SHOW", [
    "DATABASES",
    "TABLES",
    chain("COLUMNS", "FROM", tableName),
    chain("CREATE", "TABLE", tableName),
  ])((ast) => {
    const tableInfo = get(ast, [1, 2, 2]);
    const result = {
      type: "statement",
      variant: "show",
      name: ast,
      tableInfo: tableInfo?.variant === "tableName" ? tableInfo : undefined,
    };
    return result;
  });
};

// --------- DESCRIBE statement -----------
const describeStatement = () => {
  return chain(
    ["DESCRIBE", "DESC"],
    tableName,
    optional([filedName, wordSym])
  )((ast) => {
    const tableInfo = get(ast, [1]);
    const filedName = get(ast, [2]);
    const result = {
      type: "statement",
      variant: "describe",
      tableInfo: tableInfo?.variant === "tableName" ? tableInfo : undefined,
      filedName: filedName?.variant === "column" ? filedName : undefined,
    };
    return result;
  });
};

// -------- EXPLAIN statement ----------

const explainStatement = () => {
  return chain("EXPLAIN", statement)();
};

// --------- USE statement --------------
const useStatement = () => {
  return chain("USE", tableName)();
};

// -------------- TRUNCATE -------------
const truncateStatement = () => {
  return chain(
    "TRUNCATE",
    optional("TABLE"),
    tableName,
    optional(["DROP", "REUSE"], "STORAGE")
  )();
};

// ---------- 公用表达式 Common Table Expression -------
const commonTableExpression = () => {
  return chain(
    "WITH",
    chain(cteAsStatement, many(",", cteAsStatement)),
    statement
  )();
};
const cteAsStatement = () => {
  return chain(tableNameSym, "AS", "(", selectStatement, ")")();
};
